package br.com.simtecnologia.access.controll.impl;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;

import net.vidageek.mirror.dsl.Mirror;
import net.vidageek.mirror.invoke.dsl.MethodHandler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.com.caelum.vraptor.Result;
import br.com.caelum.vraptor.core.MethodInfo;
import br.com.caelum.vraptor.resource.ResourceMethod;
import br.com.simtecnologia.access.controll.LogicRoute;
import br.com.simtecnologia.util.MethodSerializable;

public class DefaultLogicRoute implements LogicRoute {
	private static final long serialVersionUID = 6822848086380876377L;
	private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLogicRoute.class);
	private static void debug(String regex, Object ... objects) {
		if (LOGGER.isDebugEnabled())
			LOGGER.debug(String.format(regex, objects));
	}
	private final MethodSerializable logic;
	private final Class<?> resource;
	private transient Object[] defaultParameters;
	private final int argsSize;
	
	private void writeObject(ObjectOutputStream os) throws IOException {
		os.defaultWriteObject();
		boolean hasDefaultParameters = this.defaultParameters != null; 
		os.writeBoolean(hasDefaultParameters);
		if (hasDefaultParameters) {
			Object[] defaultParameters = new Object[this.defaultParameters.length];
			for(int i = 0; i < defaultParameters.length; i++) {
				defaultParameters[i] = (this.defaultParameters[i] instanceof Serializable) ?
						this.defaultParameters[i] : null;
			}
			try {
				os.writeObject(defaultParameters);
			} catch (Exception e) {
				LOGGER.error("Cold not serialize one of parameters of this LogicRoute [%s] ");
			}
		}
	}
	
	private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
		is.defaultReadObject();
		boolean hasDefaultParameters = is.readBoolean();
		if (hasDefaultParameters) {
			try {
				defaultParameters = (Object[]) is.readObject();
			} catch (Exception e) {
				defaultParameters = new Object[argsSize];
			}
		}
	}
	
	DefaultLogicRoute(Class<?> clazz, Method method) {
		this(clazz,method,null);
	}
	DefaultLogicRoute(Class<?> clazz, Method method,Object[] parameters) {
		this.logic = new MethodSerializable(method);
		this.resource = clazz;
		argsSize = logic.getMethod().getParameterTypes().length;
		if (parameters == null) {
			this.defaultParameters = isWithoutArgs() ? null : new Object[argsSize];
		} else {
			validParameters(parameters);
			this.defaultParameters = isWithoutArgs() ? null : parameters;
		}
	}
	DefaultLogicRoute(ResourceMethod resourceMethod) {
		this(resourceMethod,null);
	}
	DefaultLogicRoute(ResourceMethod resourceMethod,Object[] parameters) {
		this(
				resourceMethod.getResource().getType(),
				resourceMethod.getMethod(),
				parameters
			);
	}
	DefaultLogicRoute(MethodInfo methodInfo) {
		this(
				methodInfo.getResourceMethod().getResource().getType(),
				methodInfo.getResourceMethod().getMethod(),
				methodInfo.getParameters()
		);
	}
	
	private void validParameters(Object[] parameters) {
		if (parameters.length != argsSize)
			throw new IllegalArgumentException(
					String.format(
							"defaultParameters size [%d] is not equals to logic args size [%d]"
							,parameters.length,argsSize
					)
			);
	}
	@Override
	public Method getLogic() {
		return logic.getMethod();
	}
	@Override
	public Class<?> getResource() {
		return resource;
	}
	@Override
	public Object[] getDefaultParameters() {
		return defaultParameters == null ? null : Arrays.copyOf(defaultParameters, argsSize);
	}
	@Override
	public boolean isWithoutArgs() {
		return argsSize == 0;
	}
	@Override
	public void redirectTo(Result result) {
		redirectTo(result, null);
	}
	
	private MethodHandler invokeLogicOn(Result result) {
		return new Mirror().on(result.redirectTo(resource)).invoke().method(logic.getMethod());
	}
	@Override
	public void redirectTo(Result result,Object[] parameters) {
		if (isWithoutArgs()) {
			debug("Trying redirecting to [%s]", logic);
			invokeLogicOn(result).withoutArgs();
		} else {
			if (parameters == null)
				parameters = this.defaultParameters;
			else
				validParameters(parameters);
			
			debug("Trying redirecting to [%s] with defaultParameters %s", logic, Arrays.toString(parameters));
			
			invokeLogicOn(result).withArgs(parameters);
		}
	}

	@Override
	public int hashCode() {
		if (getLogic() == null || getResource() == null)
			return super.hashCode();
		final int prime = 31;
		int result = 1;
		result = prime * result + getLogic().hashCode();
		result = prime * result	+ getResource().hashCode();
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (getLogic() == null || getResource() == null)
			return false;
		if (!(obj instanceof DefaultLogicRoute))
			return false;
		DefaultLogicRoute other = (DefaultLogicRoute) obj;
		return getLogic().equals(other.getLogic()) &&
			getResource().equals(other.getResource());
	}
	
	
}